package com.hl.framework.core.common.util.date;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;

import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * 时间工具类
 *
 * @author 芋道源码
 */
public class DateUtils {

    /**
     * 时区 - 默认
     */
    public static final String TIME_ZONE_DEFAULT = "GMT+8";

    /**
     * 秒转换成毫秒
     */
    public static final long SECOND_MILLIS = 1000;

    public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";

    public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";

    public static final String YYYY = "yyyy";

    public static final String YYYY_MM = "yyyy-MM";

    public static final String YYYY_MM_DD = "yyyy-MM-dd";

    public static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
    public static final String HH_mm = "HH:mm";
    public static final String SS = "ss";
    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";

    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    private static final String[] PARSE_PATTERNS = {
            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"
    };

    public static String dateTime(final Date date) {
        if (date == null) {
            return StringUtils.EMPTY;
        }
        return parseDateToStr(YYYY_MM_DD, date);
    }

    public static String parseDateToStr(final String format, final Date date) {
        return new SimpleDateFormat(format).format(date);
    }

    /**
     * 将 LocalDateTime 转换成 Date
     *
     * @param date LocalDateTime
     * @return LocalDateTime
     */
    public static Date of(LocalDateTime date) {
        if (date == null) {
            return null;
        }
        // 将此日期时间与时区相结合以创建 ZonedDateTime
        ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
        // 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
        Instant instant = zonedDateTime.toInstant();
        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
        return Date.from(instant);
    }

    /**
     * 将 Date 转换成 LocalDateTime
     *
     * @param date Date
     * @return LocalDateTime
     */
    public static LocalDateTime of(Date date) {
        if (date == null) {
            return null;
        }
        // 转为时间戳
        Instant instant = date.toInstant();
        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    }

    public static Date addTime(Duration duration) {
        return new Date(System.currentTimeMillis() + duration.toMillis());
    }

    public static boolean isExpired(LocalDateTime time) {
        LocalDateTime now = LocalDateTime.now();
        return now.isAfter(time);
    }

    /**
     * 创建指定时间
     *
     * @param year  年
     * @param mouth 月
     * @param day   日
     * @return 指定时间
     */
    public static Date buildTime(int year, int mouth, int day) {
        return buildTime(year, mouth, day, 0, 0, 0);
    }

    /**
     * 创建指定时间
     *
     * @param year   年
     * @param mouth  月
     * @param day    日
     * @param hour   小时
     * @param minute 分钟
     * @param second 秒
     * @return 指定时间
     */
    public static Date buildTime(int year, int mouth, int day,
                                 int hour, int minute, int second) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, mouth - 1);
        calendar.set(Calendar.DAY_OF_MONTH, day);
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, 0); // 一般情况下，都是 0 毫秒
        return calendar.getTime();
    }

    public static Date max(Date a, Date b) {
        if (a == null) {
            return b;
        }
        if (b == null) {
            return a;
        }
        return a.compareTo(b) > 0 ? a : b;
    }

    public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
        if (a == null) {
            return b;
        }
        if (b == null) {
            return a;
        }
        return a.isAfter(b) ? a : b;
    }

    /**
     * 是否今天
     *
     * @param date 日期
     * @return 是否
     */
    public static boolean isToday(LocalDateTime date) {
        return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
    }

    /**
     * 是否昨天
     *
     * @param date 日期
     * @return 是否
     */
    public static boolean isYesterday(LocalDateTime date) {
        return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
    }


    public static long getTime2(Date startTime, Date endTime) {
        long eTime = endTime.getTime();
        long sTime = startTime.getTime();
        return (eTime - sTime);
    }

    /**
     * 计算工时(毫米)
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return 工时，精确到两位小数
     */
    public static BigDecimal calWorkMs(Date startTime, Date endTime) {
        if (startTime == null || endTime == null) {
            return new BigDecimal(0).setScale(2, RoundingMode.UNNECESSARY);
        }

        //减去休息时间段,切成多组工作时间段
        List<WorkTimeDto> workTimeDtoList = toWorkTimeList(startTime, endTime);
        List<BigDecimal> workMsList = CollUtil.newArrayList();
        workTimeDtoList.forEach(e -> {
            BigDecimal diff = new BigDecimal(getTime2(e.getStartTime(), e.getEndTime()));
            workMsList.add(diff);
        });
        return millisToHours(workMsList.stream().reduce(BigDecimal.ZERO, BigDecimal::add).longValue(), 2);
    }

    /**
     * 将毫秒转换为小时（金融级精度）
     * @param millis 毫秒数
     * @param scale 小数位数（金融通常为 6-10 位）
     * @return 精确的小时数
     */
    public static BigDecimal millisToHours(long millis, int scale) {
        return new BigDecimal(millis)
                .divide(MILLIS_IN_HOUR, scale, FINANCIAL_ROUNDING);
    }
    // 时间单位常量（使用字符串构造避免精度丢失）
    private static final BigDecimal MILLIS_IN_HOUR = new BigDecimal("3600000");
    // 金融计算推荐使用 HALF_EVEN（银行家舍入法）减少累计误差
    private static final RoundingMode FINANCIAL_ROUNDING = RoundingMode.HALF_EVEN;


    /**
     * 切成多组工作时间段
     *
     * @param startTime 工时开始时间
     * @param endTime   工时结束时间
     */
    public static List<WorkTimeDto> toWorkTimeList(Date startTime, Date endTime) {
        //工时日期
        String workDate = DateUtils.dateTime(startTime);
        //休息起止时间
        Date restStart = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, workDate + " 12:00:00");
        Date restEnd = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, workDate + " 13:00:00");

        List<WorkTimeDto> workTimeDtoList = CollUtil.newArrayList();
        int workEndAndRestStart = DateUtil
                .compare(endTime, restStart, DateUtils.YYYY_MM_DD_HH_MM_SS);//工时结束-休息开始
        int workEndAndRestEnd = DateUtil
                .compare(endTime, restEnd, DateUtils.YYYY_MM_DD_HH_MM_SS);//工时结束-休息结束
        int workStartAndRestEnd = DateUtil
                .compare(startTime, restEnd, DateUtils.YYYY_MM_DD_HH_MM_SS);//工时开始-休息结束
        int workStartAndRestStart = DateUtil
                .compare(startTime, restStart, DateUtils.YYYY_MM_DD_HH_MM_SS);//工时开始-休息开始

        //工时结束<=休息开始 或 工时开始>= 休息结束
        if (workEndAndRestStart < 0 || workEndAndRestStart == 0 || workStartAndRestEnd > 0
                || workStartAndRestEnd == 0) {
            workTimeDtoList.add(new WorkTimeDto(startTime, endTime));
            return workTimeDtoList;
        }

        //工时开始<休息开始<工时结束<=休息结束
        if (workStartAndRestStart < 0 && (workEndAndRestEnd < 0 || workEndAndRestEnd == 0)) {
            workTimeDtoList.add(new WorkTimeDto(startTime, restStart));
            return workTimeDtoList;
        }

        //工时开始<休息开始 && 工时结束>=休息结束
        if (workStartAndRestStart < 0) {
            workTimeDtoList.add(new WorkTimeDto(startTime, restStart));
            workTimeDtoList.add(new WorkTimeDto(restEnd, endTime));
            return workTimeDtoList;
        }

        //休息开始<工时开始<休息结束<工时结束
        if (workStartAndRestStart > 0 && workEndAndRestEnd > 0) {
            workTimeDtoList.add(new WorkTimeDto(restEnd, endTime));
            return workTimeDtoList;
        }
        return workTimeDtoList;
    }

    public static Date dateTime(final String format, final String ts) {
        try {
            return new SimpleDateFormat(format).parse(ts);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}
